﻿using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Google.Protobuf;
using UnityEngine;

public enum ERunStatus
{
    None,
    Connected,
    ReadySend,
    Stop,
}

public class Client
{
    public static Client Instance
    {
        get
        {
            if (Launcher.Instance == null)
            {
                return null;
            }
            return Launcher.Instance.m_Client;
        }
    }

    UdpClient m_Client = new UdpClient();
    MessageDispatcher m_Dispatcher = new MessageDispatcher();
    ERunStatus m_Status;
    //byte[] m_SendBuffer;
    //byte[] m_RecvBuffer;
    MemoryStream m_SendStream;
    MemoryStream m_RecvStream;
    Queue<byte[]> m_WaitSendMgs = new Queue<byte[]>();
    public uint m_PlayerID { private set; get; }
    public uint UserID { private set; get; }
    // pos, time
    //List<KeyValuePair<Vector3, float>> m_RecentPosXs = new List<KeyValuePair<Vector3, float>>();
    Queue<byte[]> m_RecvMsgs = new Queue<byte[]>();
    uint m_ClientTickCount;
    uint m_LastServerTickCount;
    readonly uint MAX_RECEIVE_COUNT_PER_FRAME = 10;
    uint m_CurReceiveCount;
    public Dictionary<uint, Player> m_Players = new Dictionary<uint, Player>();
    Player m_LocalPlayer;
    Player m_NetLocalPlayer;
    public uint DeltaTickCount;
    public string m_IP;
    public int m_Port;
    UDPReliableTracking m_Tracking = new UDPReliableTracking();
    BinaryWriter m_BinaryWriter;
    BinaryReader m_BinaryReader;
    List<UDPPacket> m_ResentList = new List<UDPPacket>();
    float m_LastPing;
    const float MAX_GAP_PING = 5f;
    UDPPacket m_PingPacket = new UDPPacket() { m_Cmd = (ushort)EMessageType.PING, m_Data = new byte[0] };
    List<UDPPacket> m_PendingReliablePackets = new List<UDPPacket>();
    Queue<UDPPacket> m_ExecuteReliablePacketQueue = new Queue<UDPPacket>();
    bool m_UseNetLocalPlayer = false;

    void Disconnect()
    {
        Stop(true);
    }

    public void Stop(bool needResetUI = false)
    {
        m_Status = ERunStatus.Stop;
        if (needResetUI)
        {
            foreach (var item in m_Players)
            {
                GameObject.Destroy(item.Value.gameObject);
            }
        }
        m_Players.Clear();
        if (m_RecvStream != null)
        {
            m_RecvStream.Dispose();
        }
        if (m_SendStream != null)
        {
            m_SendStream.Dispose();
        }
        if (m_BinaryReader != null)
        {
            m_BinaryReader.Dispose();
        }
        if (m_BinaryWriter != null)
        {
            m_BinaryWriter.Dispose();
        }
        m_Client.Close();
        m_Client.Dispose();
        if (needResetUI)
        {
            Launcher.Instance.Stop();
        }
    }

    ~Client()
    {
        Stop();
        LogManager.Error("~Client");
    }

    public void Start()
    {
        MessageManager.Instance.Init();

        //m_RecvBuffer = new byte[1024];
        //m_SendBuffer = new byte[1024];
        m_SendStream = new MemoryStream();
        m_RecvStream = new MemoryStream();
        m_BinaryWriter = new BinaryWriter(m_SendStream);
        m_BinaryReader = new BinaryReader(m_RecvStream);
        m_Tracking.m_OnDisconnect = Disconnect;

        m_Client.Connect(m_IP, m_Port);
        m_Dispatcher.Register<Ping>(EMessageType.PING, (msg) =>
        {
            m_LastPing = MessageManager.Instance.GetCurElapsedTime();
            //LogManager.Error(string.Format("{0} 更新Ping time={1}", UserID, m_LastPing));
        });
        m_Dispatcher.Register<Ack>(EMessageType.ACK, (msg) =>
        {
            LogManager.Error(string.Format("{0} 接收ACK cmd={1},seqId={2}, ackbits={3}, totalLength={4}", UserID, EMessageType.ACK, msg.AckSeqId, msg.AckBits, 0));
            m_Tracking.RecvAck(msg);
        });
        m_Dispatcher.Register<Hello>(EMessageType.HELLO, (msg) =>
        {
            m_Status = ERunStatus.Connected;
            UserID = (uint)msg.UserId;
            LogManager.Log("local userid=" + UserID);
            Send(MessageManager.Instance.Get<C2S_PlayerJoin>(), true);
        });
        m_Dispatcher.Register<S2C_PlayerJoin>(EMessageType.S2C_JOIN, (msg) =>
        {
            var p = FindPlayer(msg.PlayerId);
            if (p != null)
            {
                DestroyPlayer(msg.PlayerId);
            }
            if (UserID == msg.UserId)
            {
                m_PlayerID = msg.PlayerId;
                LogManager.Log("S2C_JOIN local playerId=" + msg.PlayerId);
            }
            var pos = new Vector3(msg.PosX, msg.PosY, msg.PosZ);
            CreatePlayer(msg.UserId, msg.PlayerId, msg.NickName, new ShadowState() { m_Pos = pos, m_Rot = Quaternion.Euler(0, msg.RotY, 0), m_TickCount = msg.TickCount });
            LogManager.Log("S2C_JOIN userid=" + msg.UserId);
            m_Status = ERunStatus.ReadySend;
        });
        m_Dispatcher.Register<S2C_AIJoinBatch>(EMessageType.S2C_AI_JOIN_BATCH, (msg) =>
        {
            foreach (var item in msg.Players)
            {
                var pos = new Vector3(item.PosX, item.PosY, item.PosZ);
                CreatePlayer(0, item.PlayerId, item.NickName, new ShadowState() { m_Pos = pos, m_Rot = Quaternion.Euler(0, item.RotY, 0), m_TickCount = msg.TickCount });
            }
            //LogManager.Error("S2C_AIJoinBatch count=" + msg.Players.Count);
        });
        m_Dispatcher.Register<S2C_PlayerSync>(EMessageType.S2C_SYNC, (msg) =>
        {
            string s = "m_UserID=" + UserID + ", S2C_PlayerSync=";
            foreach (var item in msg.Players)
            {
                var pos = new Vector3(item.PosX, item.PosY, item.PosZ);
                var p = FindPlayer((uint)item.PlayerId);
                if (p == null)
                {
                    continue;
                }

                if (p == m_LocalPlayer)
                {
                    if (m_NetLocalPlayer != null)
                    {
                        m_NetLocalPlayer.UpdatePos(new ShadowState() { m_Pos = pos, m_Rot = Quaternion.Euler(0, item.RotY, 0), m_TickCount = msg.TickCount });
                    }
                }
                else
                {
                    p.UpdatePos(new ShadowState() { m_Pos = pos, m_Rot = Quaternion.Euler(0, item.RotY, 0), m_TickCount = msg.TickCount });
                }
                s += "playerId=" + item.PlayerId + ", pos=" + item.PosX + "; ";
                if (item.PlayerId == m_PlayerID)
                {
                    DeltaTickCount = (msg.TickCount - m_LastServerTickCount);
                    //LogManager.Error("receive m_UserID=" + m_PlayerID + ", delta count=" + DeltaTickCount + ", last tick=" + m_LastServerTickCount +
                    //", msg.Tick=" + msg.TickCount);
                    m_LastServerTickCount = msg.TickCount;

                    //if (m_RecentPosXs.FindIndex((temp) => temp.Key == pos) >= 0)
                    //{
                    //    var dest = m_RecentPosXs.Find((temp) => temp.Key == pos);
                    //    LogManager.Error("receive m_UserID=" + m_UserID + ", pos=" + dest.Key + ", receive deltaTime=" + ((MessageManager.Instance.GetCurElapsedTime() - dest.Value) * 1000));
                    //}
                }
            }
            //LogManager.Log(s);
        });

        m_Dispatcher.Register<S2C_PlayerSyncAI>(EMessageType.S2C_SYNC_AI, (msg) =>
        {
            string s = "m_UserID=" + UserID + ", S2C_SYNC_AI";
            foreach (var item in msg.Players)
            {
                var pos = new Vector3(item.PosX, item.PosY, item.PosZ);
                var p = FindPlayer((uint)item.PlayerId);
                if (p == null)
                {
                    continue;
                    //p = CreatePlayer((uint)item.PlayerId, pos, item.);
                }
                p.UpdatePos(new ShadowState() { m_Pos = pos, m_Rot = Quaternion.Euler(0, item.RotY, 0), m_TickCount = msg.TickCount });
                s += "playerId=" + item.PlayerId + ", pos=" + item.PosX + "; ";
            }
            //LogManager.Log(s);
        });
        m_Dispatcher.Register<S2C_ResendTest>(EMessageType.S2C_RESEND_TEST, (msg) =>
        {
            LogManager.Error("收到 S2C_RESEND_TEST Content=" + msg.Content);
        });

        m_Dispatcher.Register<S2C_OrderTest>(EMessageType.S2C_ORDER_TEST, (msg) =>
        {
            LogManager.Error("收到 S2C_ORDER_TEST Content=" + msg.Content);
        });

        Task.Run(RecvThread);
        Task.Run(SendThread);
        Task.Run(KeepAliveThread);

        Send(MessageManager.Instance.Get<Hello>());

        //// main thread
        //while (true)
        //{
        //    Tick();
        //    Thread.Sleep(33 * 3);
        //}
    }

    public Player CreatePlayer(uint userId, uint playerId, string nickName, ShadowState state)
    {
        var p = FindPlayer(playerId);
        if (p != null)
        {
            return p;
        }

        p = GameObject.Instantiate(Resources.Load<Player>("Player"));
        p.Init(userId, playerId, nickName, state);
        if (userId == 0)
        {
            //LogManager.Error("playerId=" + playerId + ", pos=" + state.m_Pos);
        }

        if (playerId == m_PlayerID)
        {
            m_LocalPlayer = p;

            if (m_UseNetLocalPlayer)
            {
                var p1 = GameObject.Instantiate(Resources.Load<Player>("Player"));
                p1.Init(userId, playerId + 1000, nickName + "_Net", state);
                m_NetLocalPlayer = p1;
                m_Players.Add(playerId + 1000, p1);
            }
        }
        m_Players.Add(playerId, p);
        return p;
    }

    public void DestroyPlayer(uint playerId)
    {
        var p = FindPlayer(playerId);
        if (p == null)
        {
            return;
        }
        if (playerId == m_PlayerID && m_NetLocalPlayer != null)
        {
            m_Players.Remove(m_NetLocalPlayer.PlayerID);
            GameObject.Destroy(m_NetLocalPlayer.gameObject);
        }
        GameObject.Destroy(p.gameObject);
        m_Players.Remove(playerId);
    }

    public Player GetLocalPlayer()
    {
        return m_LocalPlayer;
    }

    public Player FindPlayer(uint playerId)
    {
        if (m_Players.ContainsKey(playerId))
        {
            return m_Players[playerId];
        }
        return null;
    }

    public void Tick()
    {
        if (m_Status == ERunStatus.ReadySend)
        {
            if (m_LastPing > 0 && MessageManager.Instance.GetCurElapsedTime() > m_LastPing + MAX_GAP_PING)
            {
                LogManager.Error("Ping超时 gap=" + (MessageManager.Instance.GetCurElapsedTime() - m_LastPing));
                Stop(true);
                return;
            }

            if (m_ClientTickCount % 3 == 0)
            {
                var msg = MessageManager.Instance.Get<C2S_PlayerSync>();
                var v = m_LocalPlayer.CacheTrans.position;
                msg.PosX = v.x;
                msg.PosY = v.y;
                msg.PosZ = v.z;
                msg.RotY = m_LocalPlayer.CacheTrans.rotation.y;
                //msg.TickCount = m_TickCount;
                //LogManager.Log("id=" + UserID + ", send pos=" + v + ", m_ClientTickCount=" + m_ClientTickCount);
                //if (m_RecentPosXs.Count > 5 * 4)
                //{
                //    m_RecentPosXs.RemoveAt(0);
                //}
                //m_RecentPosXs.Add(new KeyValuePair<Vector3, float>(v, MessageManager.Instance.GetCurElapsedTime()));
                Send(msg);
            }

            foreach (var item in m_Players)
            {
                item.Value.Tick();
            }
            m_Tracking.Update(m_ResentList);
            //LogManager.Error(string.Format("{0} 重发 count={1}", UserID, m_ResentList.Count));
            foreach (var packet in m_ResentList)
            {
                Send(packet);

                var data = m_Tracking.DebugGetTrackingData(packet);
                LogManager.Error(string.Format("{0} 重发 cmd={1},seqId={2}, orderId={3}, resend_count={4}", UserID, (EMessageType)packet.m_Cmd, packet.m_SeqId, packet.m_OrderId, data == null ? 0 : data.m_ResendCount));
            }
            m_ClientTickCount++;
        }

        m_CurReceiveCount = 0;
        while (m_RecvMsgs.Count > 0)
        {
            var m = m_RecvMsgs.Dequeue();
            ProcessMessage(m);
            m_CurReceiveCount++;
            if (m_CurReceiveCount >= MAX_RECEIVE_COUNT_PER_FRAME / 2)
            {
                break;
            }
        }

        // process reliable messages. 
        m_CurReceiveCount = 0;
        while (m_ExecuteReliablePacketQueue.Count > 0)
        {
            var m = m_ExecuteReliablePacketQueue.Dequeue();
            ExecuteMessage(m);
            m_CurReceiveCount++;
            if (m_CurReceiveCount >= MAX_RECEIVE_COUNT_PER_FRAME / 2)
            {
                break;
            }
        }
    }

    public void RecvThread()
    {
        IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
        while (true)
        {
            if (m_Status == ERunStatus.Stop)
            {
                return;
            }
            try
            {
                var bs = m_Client.Receive(ref ep);
                //LogManager.Error("Receive ep=" + ep + ", bs=" + bs.Length);
                m_RecvMsgs.Enqueue(bs);
            }
            catch (Exception ex)
            {
                Stop(true);
                LogManager.Error("ex.Message=" + ex.Message);
            }
            Thread.Sleep(1);
        }
    }

    public void SendThread()
    {
        while (true)
        {
            if (m_Status == ERunStatus.Stop)
            {
                return;
            }
            //LogManager.Log("SendThread " + ", m_WaitSendMgs.Count=" + m_WaitSendMgs.Count);
            while (m_WaitSendMgs.Count > 0)
            {
                var m = m_WaitSendMgs.Dequeue();
                //LogManager.Log("SendThread send count=" + m.Length + ", m_WaitSendMgs.Count=" + m_WaitSendMgs.Count);
                m_Client.Send(m, m.Length);
            }
            Thread.Sleep(1);
        }
    }

    public void KeepAliveThread()
    {
        while (true)
        {
            if (m_Status == ERunStatus.Stop)
            {
                return;
            }
            try
            {
                Send(m_PingPacket);
                Thread.Sleep((int)(MAX_GAP_PING * 1000) / 2);
            }
            catch (Exception ex)
            {
                LogManager.Error("KeepAliveThread ex=" + ex.Message);
            }
        }
    }

    public void Send(IMessage msg, bool isReliable = false)
    {
        UDPPacket packet = new UDPPacket();
        var cmd = MessageManager.Instance.GetMessageType(msg);
        packet.m_IsReliable = isReliable;
        packet.m_Cmd = (ushort)cmd;
        packet.m_Data = msg.ToByteArray();
        Send(packet);

        if (cmd == EMessageType.C2S_RESEND_TEST)
        {
            LogManager.Error(string.Format("{0} 发送 cmd={1},seqId={2}, orderId={3}, content={4}", UserID, (EMessageType)packet.m_Cmd, packet.m_SeqId, packet.m_OrderId, (msg as C2S_ResendTest).Content));
        }
    }

    public void Send(UDPPacket packet)
    {
        if (packet == null)
        {
            return;
        }

        // for test
        //if (packet.m_Cmd == (ushort)EMessageType.C2S_SYNC)
        //{
        //    return;
        //}

        if (packet.m_IsReliable)
        {
            m_Tracking.ApplyReliableInfo(packet);
        }
        else
        {
            //m_MessageManager.Put(packet.m_Data);
        }

        lock (m_SendStream)
        {
            m_SendStream.Seek(0, SeekOrigin.Begin);
            m_BinaryWriter.Flush();
            m_BinaryWriter.Write(packet.m_Cmd);
            m_BinaryWriter.Write(packet.m_IsReliable);
            m_BinaryWriter.Write(packet.m_SeqId);
            m_BinaryWriter.Write(packet.m_OrderId);
            m_BinaryWriter.Write(packet.m_Data);
            var bs = new byte[m_SendStream.Position];
            Buffer.BlockCopy(m_SendStream.GetBuffer(), 0, bs, 0, bs.Length);
            m_WaitSendMgs.Enqueue(bs);
        }
        string s = "";
        //foreach (var item in bs)
        //{
        //    s += item + ", ";
        //}
        //LogManager.Error(string.Format("{0} 发送 cmd={1},seqId={2}, orderId={3}, totalLength={4}, s={5}", UserID, (EMessageType)packet.m_Cmd, packet.m_SeqId, packet.m_OrderId, bs.Length, s));
    }

    public bool IsConnect()
    {
        return m_Status == ERunStatus.Connected || m_Status == ERunStatus.ReadySend;
    }

    public void ProcessMessage(byte[] bs)
    {
        if (bs == null)
        {
            return;
        }

        var cmd = (EMessageType)BitConverter.ToUInt16(bs, 0);
        if (cmd != EMessageType.HELLO && !IsConnect())
        {
            return;
        }

        //LogManager.Log("ProcessMessage cmd=" + cmd);
        UDPPacket packet = new UDPPacket();
        m_RecvStream.Seek(0, SeekOrigin.Begin);
        m_RecvStream.Write(bs, 0, bs.Length);
        long lastPosition = m_RecvStream.Position;
        m_RecvStream.Seek(0, SeekOrigin.Begin);
        packet.m_Cmd = m_BinaryReader.ReadUInt16();
        packet.m_IsReliable = m_BinaryReader.ReadBoolean();
        packet.m_SeqId = m_BinaryReader.ReadUInt16();
        packet.m_OrderId = m_BinaryReader.ReadUInt16();
        packet.m_Data = m_BinaryReader.ReadBytes((int)(lastPosition - m_RecvStream.Position));

        string s = "";
        foreach (var item in bs)
        {
            s += item + ", ";
        }
        //LogManager.Error(string.Format("{0} 接收 cmd={1},seqId={2}, orderId={3}, totalLength={4}, s={5}", UserID, (EMessageType)packet.m_Cmd, packet.m_SeqId, packet.m_OrderId, bs.Length, s));

        var type = MessageManager.Instance.GetMessageSystemType(cmd);
        var m = MessageManager.Instance.Get(type);
        try
        {
            m.MergeFrom(packet.m_Data);
        }
        catch (Exception ex)
        {
            LogManager.Error("ex.Message=" + ex.Message);
        }

        if (packet.m_IsReliable)
        {
            //LogManager.Error(string.Format("{0} 发送ACK cmd={1},seqId={2}, ackBits={3}, totalLength={4}", UserID, (EMessageType)packet.m_Cmd, packet.m_SeqId, m_Tracking.GenAckBits(packet.m_SeqId), bs.Length));
            m_Tracking.AckSent(packet.m_SeqId);
            Send(new Ack() { AckSeqId = packet.m_SeqId, AckBits = m_Tracking.GenAckBits(packet.m_SeqId) });

            if (cmd != EMessageType.HELLO)
            {
                var status = m_Tracking.RecvReliablePacket(packet);
                if (status == ERecvReliableStatus.Enqueue)
                {
                    m_Dispatcher.Invoke(cmd, m);

                    status = ERecvReliableStatus.Pending;
                    if (m_PendingReliablePackets.Count > 0)
                    {
                        packet = m_PendingReliablePackets[0];
                        status = m_Tracking.RecvReliablePacket(packet);
                    }
                    while (status == ERecvReliableStatus.Enqueue || status == ERecvReliableStatus.Drop)
                    {
                        if (status == ERecvReliableStatus.Enqueue)
                        {
                            m_ExecuteReliablePacketQueue.Enqueue(packet);
                        }
                        m_PendingReliablePackets.RemoveAt(0);
                        status = ERecvReliableStatus.Pending;
                        if (m_PendingReliablePackets.Count > 0)
                        {
                            packet = m_PendingReliablePackets[0];
                            status = m_Tracking.RecvReliablePacket(packet);
                        }
                    }
                }
                else if (status == ERecvReliableStatus.Pending)
                {
                    m_PendingReliablePackets.Add(packet);
                    m_PendingReliablePackets.Sort((x, y) => { return x == y ? 0 : (CircularValue.Greater(x.m_SeqId, y.m_SeqId) ? 1 : -1); });
                }
                else
                {
                    // drop. 
                }
            }
            else
            {
                m_Dispatcher.Invoke(cmd, m);
            }
        }
        else
        {
            m_Dispatcher.Invoke(cmd, m);
        }
    }

    public void ExecuteMessage(UDPPacket packet)
    {
        var cmd = (EMessageType)packet.m_Cmd;
        var type = MessageManager.Instance.GetMessageSystemType(cmd);
        var m = MessageManager.Instance.Get(type);
        try
        {
            m.MergeFrom(packet.m_Data);
            m_Dispatcher.Invoke(cmd, m);
        }
        catch (Exception ex)
        {
            LogManager.Error("ex.Message=" + ex.Message);
        }
    }

    public void ResendTest()
    {
        Send(new C2S_ResendTest() { Content="Resend Test 001"}, true);
    }

    uint m_Index = 0;
    public void OrderTest()
    {
        Send(new C2S_OrderTest() { Content = "Order Test " + m_Index }, true);
        m_Index++; 
    }
}